{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "67cd69c9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "债券久期：30年\n",
      "时间区间\t\t债券(NPV)涨幅\t上证涨幅\n",
      "2022-07-05 ~ 2022-10-31\t4.86%\t\t-15.00%\n",
      "2023-05-09 ~ 2024-02-05\t10.38%\t\t-19.52%\n",
      "2024-05-08 ~ 2024-09-18\t8.43%\t\t-13.14%\n",
      "2024-11-07 ~ 2024-11-26\t0.76%\t\t-6.08%\n",
      "2024-12-10 ~ 2025-01-13\t3.40%\t\t-7.65%\n",
      "2025-03-31 ~ 2025-04-07\t4.53%\t\t-7.17%\n",
      "\n",
      "累计（6个区间）债券(30年)涨幅：32.36%  上证：-68.56%\n"
     ]
    }
   ],
   "source": [
    "import akshare as ak\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# === 可配置变量：将下方 tenor 改为 \"30年\" 或 \"10年\" 等 ===\n",
    "tenor = \"30年\"\n",
    "# =======================================================\n",
    "\n",
    "# 从 tenor 提取年数作为久期\n",
    "duration_years = int(tenor.rstrip(\"年\"))\n",
    "\n",
    "# 时间区间列表\n",
    "time_ranges = [\n",
    "    (\"2022-07-05\", \"2022-10-31\"),\n",
    "    (\"2023-05-09\", \"2024-02-05\"),\n",
    "    (\"2024-05-08\", \"2024-09-18\"),\n",
    "    (\"2024-11-07\", \"2024-11-26\"),\n",
    "    (\"2024-12-10\", \"2025-01-13\"),\n",
    "    (\"2025-03-31\", \"2025-04-07\"),\n",
    "]\n",
    "\n",
    "# 净现值法计算函数\n",
    "def bond_price(face_value, coupon_rate, years, yield_rate):\n",
    "    coupon_payment = face_value * coupon_rate\n",
    "    cash_flows = [coupon_payment / (1 + yield_rate) ** t for t in range(1, years + 1)]\n",
    "    cash_flows.append(face_value / (1 + yield_rate) ** years)\n",
    "    return np.sum(cash_flows)\n",
    "\n",
    "# 计算区间债券涨跌幅\n",
    "def bond_pct_change(start_str, end_str):\n",
    "    s = start_str.replace(\"-\", \"\")\n",
    "    e = end_str.replace(\"-\", \"\")\n",
    "    yd = ak.bond_china_yield(start_date=s, end_date=e)\n",
    "    yd[\"日期\"] = pd.to_datetime(yd[\"日期\"])\n",
    "    df = yd.loc[\n",
    "        yd[\"曲线名称\"] == \"中债国债收益率曲线\",\n",
    "        [\"日期\", tenor]\n",
    "    ].dropna().sort_values(\"日期\").reset_index(drop=True)\n",
    "    if len(df) < 2:\n",
    "        raise ValueError(\"债券数据不足\")\n",
    "    # 区间首日收益率作为固定票面利率\n",
    "    coupon_rate = df[tenor].iloc[0] / 100\n",
    "    df[\"price_npv\"] = df[tenor].apply(lambda y: bond_price(100.0, coupon_rate, duration_years, y / 100))\n",
    "    p0, p1 = df[\"price_npv\"].iloc[0], df[\"price_npv\"].iloc[-1]\n",
    "    return (p1 - p0) / p0 * 100\n",
    "\n",
    "# 计算区间上证指数涨跌幅\n",
    "def sh_pct_change(start_str, end_str):\n",
    "    df = ak.stock_zh_index_daily(symbol=\"sh000001\")\n",
    "    df[\"date\"] = pd.to_datetime(df[\"date\"])\n",
    "    sub = df[(df[\"date\"] >= start_str) & (df[\"date\"] <= end_str)].sort_values(\"date\")\n",
    "    if len(sub) < 2:\n",
    "        raise ValueError(\"上证指数数据不足\")\n",
    "    return (sub[\"close\"].iloc[-1] - sub[\"close\"].iloc[0]) / sub[\"close\"].iloc[0] * 100\n",
    "\n",
    "# 打印结果，前面加上“久期”\n",
    "print(f\"债券久期：{tenor}\")\n",
    "print(\"时间区间\\t\\t债券(NPV)涨幅\\t上证涨幅\")\n",
    "total_bond = total_sh = valid = 0\n",
    "\n",
    "for s, e in time_ranges:\n",
    "    try:\n",
    "        bp = bond_pct_change(s, e)\n",
    "        sp = sh_pct_change(s, e)\n",
    "        print(f\"{s} ~ {e}\\t{bp:.2f}%\\t\\t{sp:.2f}%\")\n",
    "        total_bond += bp\n",
    "        total_sh += sp\n",
    "        valid += 1\n",
    "    except Exception as err:\n",
    "        print(f\"{s} ~ {e}\\t数据不足：{err}\")\n",
    "\n",
    "if valid:\n",
    "    print(f\"\\n累计（{valid}个区间）债券({tenor})涨幅：{total_bond:.2f}%  上证：{total_sh:.2f}%\")\n",
    "else:\n",
    "    print(\"无有效区间数据\")\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
